/**
 *
 * 用户处理件
 *
 * 通过this对象除了能够访问自身外还能够访问到一些功能对象，如果需要通过路由件ctx获取到数据，请提前在路由件取好传入
 * this {
 * 	common 工具类
 * 	config 配置数据
 * 	fs 文件操作模块
 * 	uploader 上传文件处理模块
 * 	mysql 数据库连接池，通过它去调用对应连接的操作方法
 * 	redis Redis数据库连接池，通过它去调用对应连接的操作方法
 * 	cron 定时任务池，通过它操作对应定时任务
 * 	resolve 这是一个函数，返回它并提供数据参数将能够返回数据给调用它的路由件
 * 	reject 这是一个函数，返回它并提供错误码code和错误内容msg两个参数将能够返回一个错误给调用它的路由件
 * }
 *
 * 注意事项：
 * 如果你的部分方法是私有的（公共方法需要调用它的）你可以为此方法名称前加下划线来区分私有和公有成员
 * 每个类方法前面最好有async关键字，否则方法可能被异步执行，而路由件在调用处理件方法时也应该有await关键字
 *
 */

class h_user {

	/**
	 *
	 * [public] 检查登录状态
	 *
	 * @param [string] type        登录类型
	 * @param [string] session 登录session
	 *
	 * @return [promise] userData 不同身份返回不同的用户数据
	 *
	 */

	async checkLogin(session) {
		if(/([a-f\d]{8}(-[a-f\d]{4}){3}-[a-f\d]{12}?)/i.test(session)) {
			const user = await this.redis.session.get(session, true)
			if(user && user.type && user.userId && user.token && user.data) {
				const _userData = await this._getUserDataByUserId(user.userId, 'userType', 'userHead', 'userName', 'userSex')
				if(!_userData) {
					await this.redis.session.delete(session)
					return this.reject('-2017', 'user not found')
				}
				const {userType, userHead, userName, userSex} = _userData
				let result
				let userData = {
					userType,
					userHead,
					userName,
					userSex
				}
				switch(userType) {
					case 'student':
						result = await this.hd.student.getStuDataByUserId(user.userId, 'stuId', 'stuName', 'stuSex')
						if(!result)
							return this.reject('-2009', 'student not found')
						await this.common.updateSessionType(session, 'student')
					break
					case 'teacher':
						result = await this.hd.teacher.getTchDataByUserId(user.userId, 'tchId', 'tchName', 'tchSex')
						if(!result)
							return this.reject('-2012', 'teacher not found')
						await this.common.updateSessionType(session, 'teacher')
					break
					default:
						userData.userType = 'normal'
						await this.common.updateSessionType(session, 'normal')
				}
				Object.assign(userData, result)
				return this.resolve(userData)
			}
			else {
				return this.reject('-2000', 'user not logined in')
			}
		}
		else {
			return this.reject('-1011', 'user type or session invalid')
		}
	}

	/**
	 *
	 * [public] 用户登录
	 *
	 * @param [object] loginData 微信小程序登录数据{wxCode,wxEncrypytedData,wxIv}
	 *
	 * @return [promise] returnData 不同身份登录返回不同用户数据
	 *
	 */

	async wxLogin(loginData) {
		const { wxCode, wxEncrypytedData, wxIv} = loginData
		const wxData = await this._wxLogin(wxCode, wxEncrypytedData, wxIv)
		const {userName, userSex, userLang, userCity, userProvince, userCountry, userHead, sessionKey, expires} = wxData
		const wxUserId = wxData.userId
		let userData = await this._getUserDataByWxUserId(wxUserId)
		let session = null
		let returnData = null
		let userId
		//判断是否首次登录
		if(!userData) {
			//添加用户微信数据
			userId = await this._addUserData(wxUserId, wxData)
			let {sessionKey, expires} = wxData
			let {session, token} = await this._createSession('normal', userId, {sessionKey, expires})
			returnData = {
				session,
				token,
				userData: {
					userType: 'normal',
					userHead,
					userName,
					userSex
				}
			}
		}
		//判断是否未绑定身份
		else if(!userData.userType || userData.userType == '') {
			userId = userData.userId
			//更新用户微信数据
			await this._updateUserData(userId, wxData, userData)
			let {session, token} = await this._createSession('normal', userId, {sessionKey, expires})
			returnData = {
				session,
				token,
				userData: {
					userType: 'normal',
					userHead,
					userName,
					userSex
				}
			}
		}
		//已经绑定身份
		else {
			userId = userData.userId
			console.log('用户ID', userId)
			//更新用户微信数据
			await this._updateUserData(userId, wxData, userData)
			let {session, token} = await this._createSession(userData.userType, userId, {sessionKey, expires})
			returnData = {
				session,
				token,
				userData: {
					userType: userData.userType,
					userHead,
					userName,
					userSex
				}
			}
			switch(userData.userType) {
				case 'student':
					console.log('学生数据', userData)
					userData = await this.hd.student.getStuDataByUserId(userId, 'stuId', 'stuName', 'stuSex')
					if(!userData)
						return this.reject('-2009', 'student not found')
				break
				case 'teacher':
					console.log('教师数据', userData)
					userData = await this.hd.teacher.getTchDataByUserId(userId, 'tchId', 'tchName', 'tchSex')
					if(!userData)
						return this.reject('-2012', 'teacher not found')
				break
				default:
					return this.reject('-2002', 'user login type invalid')
			}
			Object.assign(returnData.userData, userData)
		}
		console.log(returnData)
		return this.resolve(returnData)
	}

	async getMajorIdByUserId(userType, userId) {
		switch(userType) {
			case 'student':
				const stuData = await this.hd.student.getStuDataByUserId(userId, 'majorId')
				if(!stuData.majorId)
					return this.reject('-2031', 'student major id not found')
				return stuData.majorId
			break
			case 'teacher':
				return this.reject('-2030', 'user type invalid cannot get major id')
			break
			case 'inside':
				const insideData = await this._getInsideStuDataByMajorId(majorId, 'majorId')
				return inside.majorId
			break
			default:
				return this.reject('-2030', 'user type invalid cannot get major id')
		}
	}

	async updateUserZfAuthId(userType, userId, userZfAuthId) {
		switch(userType) {
			case 0:
				await this.mysql.cxxy.update({
					table: 'student',
					field: 'stuZfAuthId=$stuZfAuthId',
					where: 'userId=$userId',
					data: {
						stuZfAuthId: userZfAuthId,
						userId
					}
				})
			break
			case 1:
				await this.mysql.cxxy.update({
					table: 'teacher',
					field: 'tchZfAuthId=$tchZfAuthId',
					where: 'userId=$userId',
					data: {
						tchZfAuthId: userZfAuthId,
						userId
					}
				})
			break
		}
	}

	async clearUserZfAuthId(userType, userId) {
		switch(userType) {
			case 0:
				await this.mysql.cxxy.update({
					table: 'student',
					field: 'stuZfAuthId=$stuZfAuthId',
					where: 'userId=$userId',
					data: {
						stuZfAuthId: '',
						userId
					}
				})
				await this.hd.student.clearStuDataCacheByUserId(userId)
			break
			case 1:
				await this.mysql.cxxy.update({
					table: 'teacher',
					field: 'tchZfAuthId=$tchZfAuthId',
					where: 'userId=$userId',
					data: {
						tchZfAuthId: '',
						userId
					}
				})
				await this.hd.teacher.clearTchDataCacheByUserId(userId)
			break
		}
	}

	async createSession(...params) {
		return await this._createSession(...params)
	}

	async authLogin(appId, userType, userId) {
		const {authNeed} = await this.hd.sys.getAppInfo(appId, 'authNeed')
		console.log(authNeed)
		let userData
		switch(userType) {
			case 'student':
				userData  = await this.hd.student.getStuDataByUserId(userId, ...authNeed)
			break
			default:
				return this.reject('-2106', 'user type does not support auth login')
		}
		switch(appId) {
			case 'm-0001':
				return await this.hd.studio.authLogin(userType, userData)
			break
			default:
				return this.reject('-2105', 'app not found')
		}
	}

	/*====================================================*/

	/**

	 *
	 * [private] 微信登录
	 *
	 * @param [string] wxCode 微信登录code
	 * @param [string] wxEncryptedData 微信用户信息加密数据
	 * @param [string] wxIv 微信用户信息加密iv向量
	 *
	 * @return [object] 微信用户数据和sessionKey
	 *
	 */

	async _wxLogin(wxCode, wxEncrypytedData, wxIv) {
		const {appId, appSecret, grantType} = this.config.miniApp.cxxy
		const {openId, sessionKey, expires} = await this._getSessionKey(appId, appSecret, grantType, wxCode)
		const {nickName, gender, language, city, province, country, avatarUrl} = await this._decryptUserData(appId, sessionKey, openId, wxEncrypytedData, wxIv)
		return {
			userId: openId,
			userName: nickName.length > 60 ? nickName.substring(0, 60) : nickName,
			userSex: gender,
			userLang: language,
			userCity: city,
			userProvince: province,
			userCountry: country,
			userHead: avatarUrl,
			sessionKey,
			expires
		}
	}

	/**
	 *
	 * [private] 获取微信session key
	 *
	 * @param [string] appId 小程序APPID
	 * @param [string] appSecret 小程序密钥
	 * @param [string] grantType API授权模式
	 * @param [string] wxCode 微信登录code
	 *
	 * @return [object] {sessionKey,openId,expires}
	 *
	 */

	async _getSessionKey(appId, appSecret, grantType, wxCode) {
		try {
			//获取sessionKey
			const result = await this.common.reqGet({
				url: 'https://api.weixin.qq.com/sns/jscode2session',
				data: {
					appid: appId,
					secret: appSecret,
					js_code: wxCode,
					grant_type: grantType
				}
			})
			const {errcode, errmsg, session_key, openid, expires_in} = result
			if(!session_key) {
				console.error(errcode, errmsg)
				return this.reject('-2018', 'get wx session key data failed')
			}
			return {
				sessionKey: session_key,
				openId: openid,
				expires: expires_in
			}
		}
		catch(err) {
			return this.reject('-2006', 'get wx session key data failed')
		}
	}

	/**
	 *
	 * [private] 解密微信用户数据
	 *
	 * @param [string] appId 小程序APPID
	 * @param [string] sessionKey 微信session key
	 * @param [string] openId 微信用户ID
	 * @param [string] wxEncryptedData 微信用户加密数据
	 * @param [string] wxIv 微信用户信息加密iv向量
	 *
	 * @return [object] decoded 解密完成的用户数据
	 *
	 */

	async _decryptUserData(appId, sessionKey, openId, wxEncrypytedData, wxIv) {
		let decodedData = this.crypto.decryptWxData(wxEncrypytedData, sessionKey, wxIv)
		if(!decodedData.watermark || decodedData.watermark.appid !== appId) {
			return this.reject('-2004', 'appId invalid')
		}
		if(!decodedData.openId || decodedData.openId !== openId) {
			return this.reject('-2005', 'openId invalid')
		}
		return decodedData
	}

	/**
	 *
	 * [private] 添加新用户微信数据
	 *
	 * @param [string] userId 微信用户ID同openId
	 * @param [object] wxData 微信用户信息数据对象
	 *
	 * @return [number] 1为成功 0为失败
	 *
	 */

	async _addUserData(wxUserId, wxData) {
		const userId = await this.common.createRandomStr(32)
		const {userName, userSex, userHead, userLang, userCountry, userProvince, userCity} = wxData
		const result = await this.mysql.cxxy.select({
			table: 'user',
			field: 'userId',
			where: 'wxUserId=$wxUserId',
			data: {
				wxUserId
			}
		})
		if(result[0])
			return result[0].userId
		const result2 =  await this.mysql.cxxy.insert({
			table: 'user',
			field: [
				'userId',
				'wxUserId',
				'userName',
				'userSex',
				'userHead',
				'userLang',
				'userCountry',
				'userProvince',
				'userCity',
				'userJoinDate'
			],
			data: {
				userId,
				wxUserId,
				userName,
				userSex,
				userHead,
				userLang,
				userCountry,
				userProvince,
				userCity,
				userJoinDate: this.common.timestamp()
			}
		})
		if(result2 == 0) 
			return this.reject('-2007', 'add new user data failed')
		return userId
	}

	/**
	 *
	 * [private] 更新老用户微信数据，比对老数据和新数据差别，如果有差别则更新数据
	 *
	 * @param [string] userId 微信用户ID同openId 
	 * @param [object] wxData 微信用户信息数据对象
	 * @param [object] userData 旧微信用户信息数据对象
	 *
	 */

	async _updateUserData(userId, wxData, userData) {
		let updateKeys = []
		for(let key in userData) {
			if(wxData[key] && wxData[key] != userData[key] && key != 'userType' && key != 'userId') {
				updateKeys.push(`${key}=$${key}`)
			}
		}
		if(wxData.length > 0) {
			console.warn(`user ${userData.userName} data is change`)
			wxData.userId = userId
			await this.mysql.cxxy.update({
				table: 'user',
				field: updateKeys,
				where: 'userId=$userId',
				data: wxData
			})
		}
		else {
			console.log(`user ${userData.userName} data is up-to-date`)
		}
	}

	/**
	 *
	 * [private] 创建用户session
	 *
	 * @param [string] loginType 用户的登录类型
	 * @param [string] userId 微信用户ID同openId
	 * @param [object] data 存放在session的用户数据
	 *
	 * @return [string] session 用户session32位字符串
	 *
	 */

	async _createSession(loginType, userId, data, expire) {
		const session = this.common.uuid4()
		const token = await this.common.createRandomStr(10)
		await this.redis.session.set(session, {
			type: loginType,
			userId,
			data,
			token
		}, expire || this.config.frame.sessionExpires[loginType] || this.config.frame.sessionExpires.all || 86400)
		return {
			session,
			token
		}
	}

	/**
	 *
	 * [private] 根据微信用户ID获取用户微信数据
	 *
	 * @param [string] userId  微信用户ID同openId
	 *
	 * @return [object/null] result[0] 微信用户数据
	 *
	 */

	async _getUserDataByWxUserId(wxUserId) {
		if(!wxUserId)
			return this.reject('-2020', 'wx user id invalid')
		const result = await this.mysql.cxxy.select({
			table: 'user',
			field: [
				'userId',
				'wxUserId',
				'userName',
				'userSex',
				'userHead',
				'userLang',
				'userCountry',
				'userProvince',
				'userCity',
				'userType'
			],
			where: 'wxUserId=$wxUserId',
			data: {
				wxUserId
			}
		})
		console.log(result)
		if(result[0]) {
			return result[0]
		}
		else {
			return null
		}
	}

	async _getUserDataByUserId(userId, ...fields) {
		if(!userId)
			return this.reject('-2001', 'user id invalid')
		const result = await this.mysql.cxxy.select({
			table: 'user',
			field: fields || ['userId','userName','userSex','userHead','userLang','userCountry','userProvince','userCity','userType'],
			where: 'userId=$userId',
			data: {
				userId
			}
		})
		console.log(result)
		if(result[0]) {
			return result[0]
		}
		else {
			return null
		}
	}

}

module.exports = h_user

/** 废弃函数 **/
// async _mergeTchData(teacherList) {
// 	const result = await this.mysql.cxxy.select('teacher', 'tchCourseId, tchName', `tchName in(${teacherList.join(',').replace(/[\u4e00-\u9fa5_a-zA-Z0-9\(\)]+/g, '?')})`, teacherList)
// 	let tchCourseIdMap = {}
// 	let tchNames = new Set()
// 	let tchData = []
// 	for(let tch of result) {
// 		tchCourseIdMap[tch.tchName] = tch.tchCourseId
// 		tchNames.add(tch.tchName)
// 	}
// 	for(let name of teacherList) {
// 		//如果不存在在查询的教师名称里，那么就需要添加教师
// 		if(!tchNames.has(name)) {
// 			console.log('create new course teacher' + name)
// 			//由于未绑定教师号提前生成一个临时的用户ID
// 			const tchId = await this.common.createRandomStr(6)
// 			const tchCourseId = await this.common.createRandomStr(6)
// 			const userId = this.common.md5(this.common.trim(name + tchCourseId))
// 			tchData.push([userId, tchId, name, tchCourseId])
// 			tchCourseIdMap[name] = tchCourseId
// 			tchNames.add(name)
// 		}
// 	}
// 	if(tchData.length > 0) {
// 		const tchResult = await this.mysql.cxxy.insert('teacher', 'userId, tchId, tchName, tchCourseId', tchData)
// 		if(tchResult == 0)
// 			return this.reject('-2019', 'add new teacher data failed')
// 		console.log('add new teacher', tchData)
// 	}
// 	else
// 		console.log('teacher  already exists')
// 	return tchCourseIdMap
// }